﻿namespace SharpF2.ModelView
{
	using System;
	using System.Collections.Generic;
	using System.Collections.ObjectModel;
	using System.IO;
	using System.Linq;
	using System.Windows;
	using System.Windows.Input;
	using Commands;
	using Components;
	using Model;
	using Services;

	public class UndoManagerDialogModelView : ViewModelBase
	{
		#region Constructors

		public UndoManagerDialogModelView()
		{
			LogFileItems = new ObservableCollection<UndoLogFileItem>();
			LogFileItemsBackup = new List<UndoLogFileItem>(GetUndoLogFileItems());
			UndoFileItems = new ObservableCollection<UndoFileItem>();
			SelectedUndoFileItems = new ObservableCollection<UndoFileItem>();

			UndoLogFileItemColumns.Add(new ItemMetaData<UndoLogFileItem>
			                           	{
			                           		HeaderBindingName = "UndoManagerDialogLogFile",
			                           		PropertyName = "LogFileName",
			                           		Sortable = false
			                           	});

			UndoFileItemColumns.Add(new ItemMetaData<UndoFileItem>
			                        	{
			                        		HeaderBindingName = "UndoManagerDialogInputFile",
			                        		PropertyName = "InputFilePath",
			                        		Sortable = false
			                        	});

			UndoFileItemColumns.Add(new ItemMetaData<UndoFileItem>
			                        	{
			                        		HeaderBindingName = "UndoManagerDialogOutputFile",
			                        		PropertyName = "OutputFilePath",
			                        		Sortable = false
			                        	});

			OnPropertyChanged("LogFileItems");
			OnPropertyChanged("UndoFileItems");
			OnPropertyChanged("UndoLogFileItemColumns");
			OnPropertyChanged("UndoFileItemColumns");
		}

		#endregion

		#region Private Attributes

		private readonly ObservableCollection<ItemMetaData<UndoFileItem>> undoFileItemColumns =
			new ObservableCollection<ItemMetaData<UndoFileItem>>();

		private readonly ObservableCollection<ItemMetaData<UndoLogFileItem>> undoLogFileItemColumns =
			new ObservableCollection<ItemMetaData<UndoLogFileItem>>();

		private Boolean allFilesHasADestination;

		private DelegateCommand<DragEventArgs> dropItemsCommand;
		private DelegateCommand removeUndoFileCommand;
		private ObservableCollection<UndoFileItem> selectedUndoFileItems;
		private DelegateCommand undoCommand;

		#endregion

		#region Public Properties

		public ICommand DropItemsCommand
		{
			get
			{
				if (null == dropItemsCommand) dropItemsCommand = new DelegateCommand<DragEventArgs>(ExecuteDropItemsCommand);
				return dropItemsCommand;
			}
		}

		public ObservableCollection<UndoLogFileItem> LogFileItems { get; set; }

		public UndoLogFileItem LogFileSelectedItem
		{
			set
			{
				var selectedLogFile = value;
				AllFilesHasADestination = false;

				if (0 == UndoFileItems.Count)
					return;

				var matchedFileCount = 0;
				if (selectedLogFile != null)
				{
					foreach (var undoFileItem in UndoFileItems)
						undoFileItem.OutputFilePath = String.Empty;
					var join = selectedLogFile.RenamedFiles.Join(UndoFileItems,
					                                             o => o.InputFilePath,
					                                             i => i.InputFilePath, (o, i) =>
					                                                                   	{
					                                                                   		++matchedFileCount;
					                                                                   		i.OutputFilePath = o.OutputFilePath;
					                                                                   		return matchedFileCount;
					                                                                   	});

					AllFilesHasADestination = join.Last().Equals(UndoFileItems.Count);
					OnPropertyChanged("UndoFileItems");
				}
				else
				{
					foreach (var fileItem in UndoFileItems)
						fileItem.OutputFilePath = String.Empty;
					AllFilesHasADestination = false;
				}

				OnPropertyChanged("UndoFileItems");
			}
		}

		public ICommand RemoveUndoFileCommand
		{
			get
			{
				if (null == removeUndoFileCommand)
					removeUndoFileCommand = new DelegateCommand(ExecuteRemoveUndoFileCommand, CanExecuteRemoveUndoFileCommand);
				return removeUndoFileCommand;
			}
		}

		public ObservableCollection<UndoFileItem> SelectedUndoFileItems
		{
			get { return selectedUndoFileItems; }
			set
			{
				selectedUndoFileItems = value;
				OnPropertyChanged("SelectedUndoFileItems");
			}
		}

		public ICommand UndoCommand
		{
			get
			{
				if (null == undoCommand)
					undoCommand = new DelegateCommand(ExecuteUndoCommand, CanExecuteUndoCommand);
				return undoCommand;
			}
		}

		public ObservableCollection<ItemMetaData<UndoFileItem>> UndoFileItemColumns
		{
			get { return undoFileItemColumns; }
		}

		public ObservableCollection<UndoFileItem> UndoFileItems { get; set; }

		public ObservableCollection<ItemMetaData<UndoLogFileItem>> UndoLogFileItemColumns
		{
			get { return undoLogFileItemColumns; }
		}

		#endregion

		#region Private Properties

		private Boolean AllFilesHasADestination
		{
			get { return allFilesHasADestination; }
			set
			{
				allFilesHasADestination = value;
				if (null != undoCommand)
					undoCommand.RaiseCanExecuteChanged();
			}
		}

		private IEnumerable<UndoLogFileItem> LogFileItemsBackup { get; set; }

		#endregion

		#region Public Members

		/// <summary>
		/// Add a file.
		/// </summary>
		/// <param name="filePath">The file path to add.</param>
		public void AddFile(String filePath)
		{
			if (!FileItemsContains(filePath) && File.Exists(filePath))
				UndoFileItems.Add(new UndoFileItem(filePath, String.Empty));

			UpdateUndoLogFileItems();

			OnPropertyChanged("FileItems");
		}

		/// <summary>
		/// Add files.
		/// </summary>
		/// <param name="filePaths">The files to add.</param>
		public void AddFiles(List<String> filePaths)
		{
			foreach (var filePath in filePaths)
				if (!FileItemsContains(filePath) && File.Exists(filePath))
					UndoFileItems.Add(new UndoFileItem(filePath, String.Empty));

			UpdateUndoLogFileItems();

			OnPropertyChanged("FileItems");
		}

		/// <summary>
		/// Check if the FileItems collection contains a specific input file path.
		/// </summary>
		/// <param name="filePath">The specific input file path.</param>
		/// <returns>
		/// True if FileItems contains the specific input file path.
		/// </returns>
		public bool FileItemsContains(String filePath)
		{
			var contains = false;
			foreach (var item in UndoFileItems)
			{
				if (item.InputFilePath != filePath) continue;
				contains = true;
				break;
			}

			return contains;
		}

		/// <summary>
		/// Remove a file.
		/// </summary>
		/// <param name="file">The file to remove.</param>
		public void RemoveFile(UndoFileItem file)
		{
			UndoFileItems.Remove(file);

			UpdateUndoLogFileItems();

			OnPropertyChanged("FileItems");
		}

		/// <summary>
		/// Remove many files.
		/// </summary>
		/// <param name="files">Files to remove.</param>
		public void RemoveFiles(IEnumerable<UndoFileItem> files)
		{
			if (null == files) return;

			foreach (var file in files)
				UndoFileItems.Remove(file);

			UpdateUndoLogFileItems();

			OnPropertyChanged("FileItems");
		}

		#endregion

		#region Private Members

		/// <summary>
		/// Return a list of log file found.
		/// </summary>
		/// <returns>
		/// The list of log file found.
		/// </returns>
		private static IEnumerable<UndoLogFileItem> GetUndoLogFileItems()
		{
			var directoryPath = AppDomain.CurrentDomain.BaseDirectory + @"Logs\";
			var undoLogFileItems = ScanDirectory(directoryPath);
			return undoLogFileItems;
		}

		/// <summary>
		/// Return a list of log file found in a specific directory.
		/// </summary>
		/// <param name="directoryPath">The specific directory.</param>
		/// <returns>
		/// The list of log file found.
		/// </returns>
		private static IEnumerable<UndoLogFileItem> ScanDirectory(String directoryPath)
		{
			var undoLogFileItems = new List<UndoLogFileItem>();
			if (!Directory.Exists(directoryPath))
				return undoLogFileItems;

			var files = Directory.GetFiles(directoryPath);
			foreach (var file in files)
				if (Path.GetExtension(file) == ".log")
					undoLogFileItems.Add(new UndoLogFileItem(file));

			return undoLogFileItems;
		}

		private bool CanExecuteRemoveUndoFileCommand()
		{
			return null != SelectedUndoFileItems && SelectedUndoFileItems.Count() > 0;
		}

		private Boolean CanExecuteUndoCommand()
		{
			return AllFilesHasADestination;
		}

		private void ExecuteDropItemsCommand(DragEventArgs e)
		{
			if (!(e.Data is DataObject) || !((DataObject) e.Data).ContainsFileDropList()) return;
			foreach (var filePath in ((DataObject) e.Data).GetFileDropList())
				AddFile(filePath);
		}

		private void ExecuteRemoveUndoFileCommand()
		{
			RemoveFiles(SelectedUndoFileItems);
		}

		private void ExecuteUndoCommand()
		{
			LoggingService.Instance.NewFile();
			foreach (var fileItem in UndoFileItems)
			{
				try
				{
					File.Move(fileItem.InputFilePath, fileItem.OutputFilePath);
					LoggingService.Instance.WriteToLog("Succeed to move file from " + fileItem.InputFilePath + " to " +
					                                   fileItem.OutputFilePath);
				}
				catch (Exception ex)
				{
					// Ignore - Log
					LoggingService.Instance.WriteToLog("Fail to move file, Exception source : " + fileItem.InputFilePath + " to " +
					                                   fileItem.OutputFilePath + " " + ex.Message);
				}
			}
		}

		/// <summary>
		/// Search in the log files found witch contains renaming event for each inserted files.
		/// </summary>
		private void UpdateUndoLogFileItems()
		{
			LogFileItems.Clear();

			if (0 == UndoFileItems.Count)
				return;

			foreach (var logFileItem in LogFileItemsBackup)
			{
				var localLogFileItem = logFileItem;
				if (UndoFileItems.All(
					o => localLogFileItem.RenamedFiles.Exists(
					     	l => o.InputFilePath.Equals(l.InputFilePath))))
					LogFileItems.Add(logFileItem);
			}

			if (null == LogFileItems || LogFileItems.Count == 0) return;
			LogFileSelectedItem = LogFileItems.First();

			OnPropertyChanged("LogFileSelectedItem");
			OnPropertyChanged("UndoLogFileItems");
		}

		#endregion
	}
}